﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
using LaserTag.Infrastructure;
using LaserTag.Infrastructure.Events;
using Microsoft.Practices.Composite.Events;
using Microsoft.Practices.Composite.Presentation.Events;
using Microsoft.Xna.Framework;
using XNAGraffiti;
using Point=System.Windows.Point;

namespace LaserTag.Module.Graffiti.Controllers
{
    public class GraffitiWallController : IGraffitiWallController
    {
        private XnaGraffitiGame m_game;
        private readonly IEventAggregator m_eventAggregator;
        private Point? m_lastTargetCenter;
        private Screen m_lastScreen;
        private GraffitiEffect m_lastGraffitiEffect;

        private const int TARGET_MOVED_THRESHOLD = 5;
        private const int NEW_LINE_SEGMENT_TIMEOUT_MS = 250;

        public GraffitiWallController(IEventAggregator eventAggregator)
        {
            m_eventAggregator = eventAggregator;
        }

        public void Run()
        {
            m_eventAggregator.GetEvent<StartGraffitiEvent>().Subscribe(ShowGraffitiWall, ThreadOption.UIThread, true);

            m_eventAggregator.GetEvent<NewObjectsDetectedEvent>().Subscribe(OnNewObjectsDetected /* Callback method */,
                                                                            ThreadOption.UIThread, /* Callback using UI thread */
                                                                            true /* Use strong references */);

            m_eventAggregator.GetEvent<GraffitiScreenChangedEvent>().Subscribe(OnGraffitiScreenChanged /* Callback method */,
                                                                            ThreadOption.UIThread, /* Callback using UI thread */
                                                                            true /* Use strong references */);

            m_eventAggregator.GetEvent<GraffitiEffectChangedEvent>().Subscribe(OnGraffitiEffectChanged /* Callback method */,
                                                                           ThreadOption.UIThread, /* Callback using UI thread */
                                                                           true /* Use strong references */);
        }

        private void OnGraffitiEffectChanged(GraffitiEffect effect)
        {
            m_lastGraffitiEffect = effect;

            if (m_game == null)
                return;

            m_game.DelegateDispatcher.BeginInvoke((Action)(() => m_game.Effect = effect));
        }

        private void OnGraffitiScreenChanged(Screen screen)
        {
            m_lastScreen = screen;

            if(m_game == null || screen == null)
                return;

            m_game.DelegateDispatcher.BeginInvoke((Action) (() => m_game.SetScreen(screen)));
        }

        private void ShowGraffitiWall(bool show)
        {
            if (m_game != null)
                m_game.Exit();

            if (!show) 
                return;

            var t = new Thread((ThreadStart)delegate
            {
                m_game = new XnaGraffitiGame();
                m_game.SetScreen(m_lastScreen);
                m_game.Effect = m_lastGraffitiEffect;
                m_game.Run();
                m_game.Dispose();
            });

            t.SetApartmentState(ApartmentState.STA);
            t.IsBackground = true;
            t.Start();
        }

        private DateTime? m_lastDetectTime;
        private void OnNewObjectsDetected(List<DetectedObject> objects)
        {
            if (m_game == null || objects.Count == 0)
                return;

            if (!m_lastDetectTime.HasValue)
                m_lastDetectTime = DateTime.Now;

            var timeDiff = DateTime.Now - m_lastDetectTime.Value;

            if (timeDiff.TotalMilliseconds > NEW_LINE_SEGMENT_TIMEOUT_MS)
            {
                m_lastTargetCenter = null;
                Debug.WriteLine("NEW LINE SEGMENT");
            }

            m_lastDetectTime = DateTime.Now;

            var targetCenter = objects[0].GetCenter();

            int windowWidth = m_lastScreen.Bounds.Width;
            int windowHeight = m_lastScreen.Bounds.Height;

            double widthRatio = windowWidth / objects[0].DetectionAreaSize.Width;
            double heightRatio = windowHeight / objects[0].DetectionAreaSize.Height;

            targetCenter.X *= widthRatio;
            targetCenter.Y *= heightRatio;

            if (!m_lastTargetCenter.HasValue)
                m_lastTargetCenter = targetCenter;

            var lastTargetCenter = m_lastTargetCenter.Value;
            var start = new Microsoft.Xna.Framework.Point((int) lastTargetCenter.X, (int) lastTargetCenter.Y);
            var end = new Microsoft.Xna.Framework.Point((int) targetCenter.X, (int) targetCenter.Y);
            
            if (Math.Abs(targetCenter.X - lastTargetCenter.X) >= TARGET_MOVED_THRESHOLD ||
                Math.Abs(targetCenter.Y - lastTargetCenter.Y) >= TARGET_MOVED_THRESHOLD)
            {
                m_game.DelegateDispatcher.BeginInvoke((Action)delegate
                {
                    m_game.DrawLine(start, end);
                });
                m_lastTargetCenter = targetCenter;
            }
        }
    }
}
